<template>
  <div class="page" ref="chartDom"></div>
</template>
<script>
import G6 from "@antv/g6";
import insertCss from "insert-css";
import { isEqual } from "lodash";
//基础样式
insertCss(`
  .g6-component-tooltip {
    background-color: rgba(0,0,0, 0.65);
    padding: 10px;
    box-shadow: rgb(174, 174, 174) 0px 0px 10px;
    width: fit-content;
    color: #fff;
    border-radius = 4px;
  }
`);

//递归处理数据
function disposeTreeData(data) {
  data.forEach((item) => {
    item.name = item.name + "用户";
    item.value = item?.value || "0万户";
    item.equalName = "同比";
    item.equalValue = "+18.96万户";
    item.equalPercent = "17.56%";
    item.loopName = "环比";
    item.loopValue = "+56.12万户";
    item.loopPercent = "87.11%";
    if (item.children && item.children?.length > 0) {
      disposeTreeData(item.children);
    }
  });
  return data;
}
//截取中英文混搭字符串长度
function truncateString(str, TARGET_VALUE = 22) {
  let length = 0; //字符串总长度
  let result = ""; //截取的字符串
  // TARGET_VALUE ; //要截取的长度

  for (let i = 0; i < str.length; i++) {
    const charCode = str.charCodeAt(i);
    if (charCode >= 0 && charCode <= 128) {
      length += 1;
    } else {
      length += 2;
    }
    if (length <= TARGET_VALUE) {
      result += str[i];
    }
  }

  return {
    length,
    result,
  };
}
// mocked data
const mockData1 = {
  id: "g1", //节点id   必须
  name: "测试文本长度都是对的是多少是的第三代说的", //节点名称  指标名称
  status: "B", //容器item背景色标记
  collapsed: false, //节点是否收起状态   true + 收起   false - 展开
  value: "", // 用户数
  equalName: "", //同比名
  equalValue: "", //同比数
  equalPercent: "", //同比比值
  loopName: "", //环比名
  loopValue: "", //环比数
  loopPercent: "", //环比值
  children: [
    {
      id: "g12",
      name: "Deal with LONG label LONG label LONG label LONG label",
      count: 123456,
      label: "338.00",
      rate: 0.627,
      status: "R",
      currency: "Yuan",
      variableName: "V2",
      variableValue: 0.179,
      variableUp: true,
      children: [
        {
          id: "g121",
          name: "Name3",
          collapsed: false,
          count: 123456,
          label: "138.00",
          rate: 0.123,
          status: "B",
          currency: "Yuan",
          variableName: "V2",
          variableValue: 0.27,
          variableUp: true,
          children: [
            {
              id: "g1211",
              name: "Name4",
              count: 123456,
              label: "138.00",
              rate: 1.0,
              status: "B",
              currency: "Yuan",
              variableName: "V1",
              variableValue: 0.164,
              variableUp: false,
              children: [],
            },
          ],
        },
        {
          id: "g122",
          name: "Name5",
          collapsed: true,
          count: 123456,
          label: "100.00",
          rate: 0.296,
          status: "G",
          currency: "Yuan",
          variableName: "V1",
          variableValue: 0.259,
          variableUp: true,
          children: [
            {
              id: "g1221",
              name: "Name6",
              count: 123456,
              label: "40.00",
              rate: 0.4,
              status: "G",
              currency: "Yuan",
              variableName: "V1",
              variableValue: 0.135,
              variableUp: true,
              children: [
                {
                  id: "g12211",
                  name: "Name6-1",
                  count: 123456,
                  label: "40.00",
                  rate: 1.0,
                  status: "R",
                  currency: "Yuan",
                  variableName: "V1",
                  variableValue: 0.181,
                  variableUp: true,
                  children: [],
                },
              ],
            },
            {
              id: "g1222",
              name: "Name7",
              count: 123456,
              label: "60.00",
              rate: 0.6,
              status: "G",
              currency: "Yuan",
              variableName: "V1",
              variableValue: 0.239,
              variableUp: false,
              children: [],
            },
          ],
        },
        {
          id: "g123",
          name: "Name8",
          collapsed: true,
          count: 123456,
          label: "100.00",
          rate: 0.296,
          status: "DI",
          currency: "Yuan",
          variableName: "V2",
          variableValue: 0.131,
          variableUp: false,
          children: [
            {
              id: "g1231",
              name: "Name8-1",
              count: 123456,
              label: "100.00",
              rate: 1.0,
              status: "DI",
              currency: "Yuan",
              variableName: "V2",
              variableValue: 0.131,
              variableUp: false,
              children: [],
            },
          ],
        },
      ],
    },
    {
      id: "g13",
      name: "Name9",
      count: 123456,
      label: "100.90",
      rate: 0.187,
      status: "B",
      currency: "Yuan",
      variableName: "V2",
      variableValue: 0.221,
      variableUp: true,
      children: [
        {
          id: "g131",
          name: "Name10",
          count: 123456,
          label: "33.90",
          rate: 0.336,
          status: "R",
          currency: "Yuan",
          variableName: "V1",
          variableValue: 0.12,
          variableUp: true,
          children: [],
        },
        {
          id: "g132",
          name: "Name11",
          count: 123456,
          label: "67.00",
          rate: 0.664,
          status: "G",
          currency: "Yuan",
          variableName: "V1",
          variableValue: 0.241,
          variableUp: false,
          children: [],
        },
      ],
    },
    {
      id: "g14",
      name: "Name12",
      count: 123456,
      label: "100.00",
      rate: 0.186,
      status: "G",
      currency: "Yuan",
      variableName: "V2",
      variableValue: 0.531,
      variableUp: true,
      children: [],
    },
  ],
};
const mockData = disposeTreeData([mockData1])[0];
const colors = {
  //缩略图背景色  根据status取值
  B: "#5B8FF9",
  R: "#F46649",
  Y: "#EEBC20",
  G: "#5BD8A6",
  DI: "#A7A7A7",
};
//  组件props
const props = {
  data: mockData,
  config: {
    padding: [20, 50], //画布内边距
    defaultLevel: 3, //默认展开层级
    defaultZoom: 1, //默认缩放尺寸
    minZoom: 0.25,
    maxZoom: 4,
    modes: { default: ["zoom-canvas", "drag-canvas"] }, //缩放移动插件
  },
};
// 默认配置
const defaultConfig = {
  modes: {
    default: ["zoom-canvas", "drag-canvas"],
  },
  fitView: true, //开启视口响应填满整个容器
  animate: true, //开启过渡动画
  defaultNode: {
    type: "flow-rect",
  },
  defaultEdge: {
    type: "cubic-horizontal",
    style: {
      stroke: "#CED4D9", //线条的颜色
      endArrow: {
        path: "M 0,0 L 8,4 L 8,-4 Z",
        fill: "#AFB3C3",
      },
    },
  },
  layout: {
    type: "indented",
    direction: "LR",
    dropCap: false,
    indent: 300, //左右间距
    getHeight: () => {
      return 90;
    },
  },
};
// 自定义节点、边
const registerFn = () => {
  // 自定义节点样式和内容
  G6.registerNode(
    "flow-rect",
    {
      shapeType: "flow-rect",
      draw(cfg, group) {
        const {
          name = "",
          collapsed,
          value,
          equalName,
          equalValue,
          equalPercent,
          loopName,
          loopValue,
          loopPercent,
        } = cfg;

        //最外层矩形边框
        const grey = "#CED4D9";
        const rectConfig = {
          width: 202, //单个item矩形的宽度
          height: 90, // 高度
          lineWidth: 1, //边框粗细
          fontSize: 16, //字体大小
          fill: "#fff", //背景填充色
          radius: 4, //圆角
          stroke: grey, //边框颜色
          opacity: 1,
        };

        //左上角的作为原点
        const nodeOrigin = {
          x: -rectConfig.width / 2,
          y: -rectConfig.height / 2,
        };

        const textConfig = {
          textAlign: "left",
          textBaseline: "bottom",
          fontSize: 12,
          fontWeight: 600,
        };

        //容器盒子
        const rect = group.addShape("rect", {
          attrs: {
            x: nodeOrigin.x,
            y: nodeOrigin.y,
            ...rectConfig,
          },
        });

        // label title  标题
        const title = group.addShape("text", {
          attrs: {
            ...textConfig,
            x: 12 + nodeOrigin.x,
            y: 20 + nodeOrigin.y,
            text: "名称:",
            // fontSize: 12,
            opacity: 0.85,
            fill: "#000",
            cursor: "pointer",
          },
          name: "name-shape",
        });
        group.addShape("text", {
          attrs: {
            ...textConfig,
            x: title.getBBox().maxX + 5,
            y: 20 + nodeOrigin.y,
            text:
              truncateString(name).length > 22
                ? truncateString(name).result + "..."
                : truncateString(name).result,
            // fontSize: 12,
            opacity: 0.85,
            fill: "#000",
            cursor: "pointer",
          },
          name: "name-shape",
        });

        // value 用户数
        const userNum = group.addShape("text", {
          attrs: {
            ...textConfig,
            x: 12 + nodeOrigin.x,
            y: 20 + nodeOrigin.y + 20,
            text: "户数:",
            // fontSize: 14,
            fill: "#000",
            opacity: 0.85,
          },
        });
        group.addShape("text", {
          attrs: {
            ...textConfig,
            x: userNum.getBBox().maxX + 5,
            y: 20 + nodeOrigin.y + 20,
            text: value,
            // fontSize: 14,
            fill: "#000",
            opacity: 0.85,
          },
        });

        //  equal  同比
        const eqValue = group.addShape("text", {
          attrs: {
            ...textConfig,
            x: 12 + nodeOrigin.x,
            y: 20 + nodeOrigin.y + 40,
            text: equalName + ":",
            fontSize: 12,
            fill: "#000",
            opacity: 0.75,
          },
        });
        //  equalValue
        const equal = group.addShape("text", {
          attrs: {
            ...textConfig,
            x: eqValue.getBBox().maxX + 5,
            y: 20 + nodeOrigin.y + 40,
            text: equalValue + "、",
            fontSize: 12,
            fill: "#c91717",
            opacity: 0.75,
          },
        });
        group.addShape("text", {
          attrs: {
            ...textConfig,
            x: equal.getBBox().maxX + 5,
            y: 20 + nodeOrigin.y + 40,
            text: equalPercent,
            fontSize: 12,
            fill: "#000",
            opacity: 0.75,
          },
        });
        //  loop
        const loop = group.addShape("text", {
          attrs: {
            ...textConfig,
            x: 12 + nodeOrigin.x,
            y: 20 + nodeOrigin.y + 60,
            text: loopName + ":",
            fontSize: 12,
            fill: "#000",
            opacity: 0.75,
          },
        });
        const loopVal = group.addShape("text", {
          attrs: {
            ...textConfig,
            x: loop.getBBox().maxX + 5,
            y: 20 + nodeOrigin.y + 60,
            text: loopValue + "、",
            fontSize: 12,
            fill: "#1890ff",
            opacity: 0.75,
          },
        });
        group.addShape("text", {
          attrs: {
            ...textConfig,
            x: loopVal.getBBox().maxX + 5,
            y: 20 + nodeOrigin.y + 60,
            text: loopPercent,
            fontSize: 12,
            fill: "#000",
            opacity: 0.75,
          },
        });

        // collapse rect
        if (cfg.children && cfg.children.length) {
          group.addShape("rect", {
            attrs: {
              x: rectConfig.width / 2 - 8,
              y: -8,
              width: 16,
              height: 16,
              stroke: "rgba(0, 0, 0, 0.25)",
              cursor: "pointer",
              fill: "#fff",
            },
            name: "collapse-back",
            modelId: cfg.id,
          });

          // collpase text
          group.addShape("text", {
            attrs: {
              x: rectConfig.width / 2,
              y: 1,
              textAlign: "center",
              textBaseline: "middle",
              text: collapsed ? "+" : "-",
              fontSize: 16,
              cursor: "pointer",
              fill: "rgba(0, 0, 0, 0.25)",
            },
            name: "collapse-text",
            modelId: cfg.id,
          });
        }

        this.drawLinkPoints(cfg, group);
        return rect;
      },
      //定义更新方法
      update(cfg, item) {
        //定义缩略样式
        const { level, status, name } = cfg;
        const group = item.getContainer();
        let mask = group.find((ele) => ele.get("name") === "mask-shape");
        let maskLabel = group.find(
          (ele) => ele.get("name") === "mask-label-shape"
        );
        if (level === 0) {
          group.get("children").forEach((child) => {
            if (child.get("name")?.includes("collapse")) return;
            child.hide();
          });
          if (!mask) {
            mask = group.addShape("rect", {
              attrs: {
                x: -101,
                y: -30,
                width: 202,
                height: 60,
                opacity: 0,
                fill: colors[status],
              },
              name: "mask-shape",
            });
            maskLabel = group.addShape("text", {
              attrs: {
                fill: "#fff",
                fontSize: 20,
                x: 0,
                y: 10,
                text:
                  "名称:" +
                  (truncateString(name, 13).length > 13
                    ? truncateString(name, 13).result + "..."
                    : truncateString(name, 13).result),
                textAlign: "center",
                opacity: 0,
              },
              name: "mask-label-shape",
            });
            const collapseRect = group.find(
              (ele) => ele.get("name") === "collapse-back"
            );
            const collapseText = group.find(
              (ele) => ele.get("name") === "collapse-text"
            );
            collapseRect?.toFront();
            collapseText?.toFront();
          } else {
            mask.show();
            maskLabel.show();
          }
          mask.animate({ opacity: 1 }, 200);
          maskLabel.animate({ opacity: 1 }, 200);
          return mask;
        } else {
          group.get("children").forEach((child) => {
            if (child.get("name")?.includes("collapse")) return;
            child.show();
          });
          mask?.animate(
            { opacity: 0 },
            {
              duration: 200,
              callback: () => mask.hide(),
            }
          );
          maskLabel?.animate(
            { opacity: 0 },
            {
              duration: 200,
              callback: () => maskLabel.hide(),
            }
          );
        }
        this.updateLinkPoints(cfg, group);
      },
      //定义更新节点状态的方法
      setState(name, value, item) {
        if (name === "collapse") {
          const group = item.getContainer();
          const collapseText = group.find(
            (e) => e.get("name") === "collapse-text"
          );
          if (collapseText) {
            if (!value) {
              collapseText.attr({
                text: "-",
              });
            } else {
              collapseText.attr({
                text: "+",
              });
            }
          }
        }
      },
      getAnchorPoints() {
        return [
          [0, 0.5],
          [1, 0.5],
        ];
      },
    },
    "rect"
  );

  //绘制贝塞尔曲线  线条
  G6.registerEdge(
    "flow-cubic",
    {
      getControlPoints(cfg) {
        let controlPoints = cfg.controlPoints; // 指定controlPoints
        if (!controlPoints || !controlPoints.length) {
          const { startPoint, endPoint, sourceNode, targetNode } = cfg;
          const {
            x: startX,
            y: startY,
            coefficientX,
            coefficientY,
          } = sourceNode ? sourceNode.getModel() : startPoint;
          const { x: endX, y: endY } = targetNode
            ? targetNode.getModel()
            : endPoint;
          let curveStart = (endX - startX) * coefficientX;
          let curveEnd = (endY - startY) * coefficientY;
          curveStart = curveStart > 40 ? 40 : curveStart;
          curveEnd = curveEnd < -30 ? curveEnd : -30;
          controlPoints = [
            { x: startPoint.x + curveStart, y: startPoint.y },
            { x: endPoint.x + curveEnd, y: endPoint.y },
          ];
        }
        return controlPoints;
      },
      getPath(points) {
        const path = [];
        path.push(["M", points[0].x, points[0].y]);
        path.push([
          "C",
          points[1].x,
          points[1].y,
          points[2].x,
          points[2].y,
          points[3].x,
          points[3].y,
        ]);
        return path;
      },
    },
    "single-line"
  );
};
registerFn();
export default {
  name: "ChartsG6",
  data() {
    return {
      graph: null,
    };
  },
  watch: {
    mockData: {
      handler(newVal, oldVal) {
        if (isEqual(newVal, oldVal) === false) {
          this.graph.read({ ...newVal }); //同this.graph.data
          this.graph.refreshPositions(); //树图重绘
        }
      },
      deep: true,
    },
  },
  methods: {
    createCharts(data) {
      //获取画布尺寸
      const container = this.$refs.chartDom;
      const width = container.scrollWidth;
      const height = container.scrollHeight || 500;
      if (!data) {
        return;
      }
      const { onInit, config } = props;
      //创建g6悬浮窗效果
      const tooltip = new G6.Tooltip({
        // 偏移量
        offsetX: 10,
        offsetY: 20,
        // trigger: "click",  //浮窗触发方式默认悬浮
        // 允许出现 tooltip 的 item 类型
        itemTypes: ["node"], //悬浮触发对象
        // 自定义 tooltip 内容
        getContent: (e) => {
          //支持html
          const outDiv = document.createElement("div");
          //outDiv.style.padding = '0px 0px 20px 0px';
          const nodeName = e.item.getModel().name;
          let formatedNodeName = "";
          for (let i = 0; i < nodeName.length; i++) {
            formatedNodeName = `${formatedNodeName}${nodeName[i]}`;
            if (i !== 0 && i % 20 === 0)
              formatedNodeName = `${formatedNodeName}<br/>`;
          }
          outDiv.innerHTML = `${formatedNodeName}`;
          return outDiv;
        },
        //设置缩略图和正常图的悬浮
        shouldBegin: (e) => {
          if (
            e.target.get("name") === "name-shape" ||
            e.target.get("name") === "mask-label-shape"
          )
            return true;
          return false;
        },
      });

      //创建g6树实例
      this.graph = new G6.TreeGraph({
        container: container,
        width,
        height,
        ...defaultConfig,
        ...config,
        plugins: [tooltip],
      });
      if (typeof onInit === "function") {
        //是否有初始化行为
        onInit(this.graph);
      }
      this.graph.data(data); //渲染
      this.graph.render();

      //设置开卡收起功能
      const handleCollapse = (e) => {
        const target = e.target;
        const id = target.get("modelId");
        const item = this.graph.findById(id);
        const nodeModel = item.getModel();
        console.log(target, "target======");
        console.log(item, "item======");
        console.log(nodeModel, "nodeModel======");
        nodeModel.collapsed = !nodeModel.collapsed;
        this.graph.layout();
        this.graph.setItemState(item, "collapse", nodeModel.collapsed);
      };
      //监听事件
      this.graph.on("collapse-text:click", (e) => {
        handleCollapse(e);
      });
      this.graph.on("collapse-back:click", (e) => {
        handleCollapse(e);
      });

      // 监听画布缩放，缩小到一定程度，节点显示缩略样式

      let currentLevel = 1;
      const briefZoomThreshold = 0.5; //0.8倍时展示缩略形式化  但是高放大比浏览器不适合该功能
      this.graph.on("viewportchange", (e) => {
        if (e.action !== "zoom") return;
        const currentZoom = this.graph.getZoom();
        let toLevel = currentLevel;
        if (currentZoom < briefZoomThreshold) {
          toLevel = 0;
        } else {
          toLevel = 1;
        }
        if (toLevel !== currentLevel) {
          currentLevel = toLevel;
          this.graph.getNodes().forEach((node) => {
            this.graph.updateItem(node, {
              level: toLevel,
            });
          });
        }
      });

      //视口变化
      if (typeof window !== "undefined")
        window.onresize = () => {
          if (!this.graph || this.graph.get("destroyed")) return;
          if (!container || !container.scrollWidth || !container.scrollHeight)
            return;
          this.graph.changeSize(container.scrollWidth, container.scrollHeight);
        };
    },
  },
  mounted() {
    this.createCharts(mockData);
  },
};
</script>
<style scoped lang="less">
.page {
  width: 100%;
  height: 800px;
  border: 1px red solid;
}
</style>
